package snapex.core.security;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.state.StateKeyGenerator;
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.resource.UserApprovalRequiredException;
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.authentication.TokenExtractor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import lombok.extern.slf4j.Slf4j;
import snapex.EncryptablePropertiesConfiguration;

@Slf4j
@Configuration
@AutoConfigureAfter(EncryptablePropertiesConfiguration.class)
@EnableOAuth2Sso
public class SsoConfiguration extends WebSecurityConfigurerAdapter {

	@Value("${snapex.security.enabled}")
	boolean isSecurityEnabled;
	
	@Autowired @Qualifier("JwtTokenExtractor")
	TokenExtractor tokenExtractor;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		if(isSecurityEnabled) {
			
			AntPathRequestMatcher imageMatched = new AntPathRequestMatcher("/invoice/*/image");
			
			http.requestMatcher(new RequestMatcher() {
	            @Override
	            public boolean matches(HttpServletRequest request) {
	                boolean isAuthenticationRequired = true;
	                
	                String requestURI = request.getRequestURI();
	                if(requestURI.startsWith("/user/register") || imageMatched.matches(request)) {
	                	isAuthenticationRequired = false;
	                }
	                else if(tokenExtractor.extract(request) != null) {
	                	isAuthenticationRequired = false;
	                }
	                
	                return isAuthenticationRequired;
	            }
	        })
		 	.authorizeRequests().anyRequest().authenticated()
		 	.and()
				.csrf().disable()				
				.httpBasic().disable()
				.formLogin().disable()
			;			
		}
		else {
			http.csrf().disable();
			http.authorizeRequests().anyRequest().permitAll();
		}
		
		OAuth2RestTemplate restTemplate = this.getApplicationContext().getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
		restTemplate.setAccessTokenProvider(tokenProvider());
	}
	
	private AuthorizationCodeAccessTokenProvider tokenProvider() {
		
		AuthorizationCodeAccessTokenProvider tokenProvider = new AuthorizationCodeAccessTokenProvider() {
			@Override
			public String obtainAuthorizationCode(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
					throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException,
					OAuth2AccessDeniedException {

				String code = super.obtainAuthorizationCode(details, request);

				log.debug("***obtainAuthorizationCode(...), code={}", code);

				return code;
			}

			@Override
			public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details,
					AccessTokenRequest request) throws UserRedirectRequiredException, UserApprovalRequiredException,
					AccessDeniedException, OAuth2AccessDeniedException {

				OAuth2AccessToken token = super.obtainAccessToken(details, request);

				log.debug("***obtainAccessToken(...), token={}", token);

				return token;
			}
		};

		tokenProvider.setStateKeyGenerator(new StateKeyGenerator() {

			private RandomValueStringGenerator generator = new RandomValueStringGenerator(16);

			@Override
			public String generateKey(OAuth2ProtectedResourceDetails resource) {

				String token = generator.generate();

				log.debug("***generateKey: {}", token);

				return token;
			}
		});

		return tokenProvider;
	}
}
